package com.bms.interceptor;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Properties;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;

import com.bms.comm.Pagination;
import com.bms.tools.MyReflectUtil;

@Intercepts({ 
				@Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class })
			})  

public class PageInterceptor implements Interceptor {
	
	private String mapperName;
	
	private String dbType;
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		// TODO Auto-generated method stub
		
		//当拦截的是 StatementHandler
		if (invocation.getTarget() instanceof StatementHandler) {  
			prepareMethod(invocation);
		}else if(invocation.getTarget() instanceof ResultSetHandler){
			Object obj = handleResultSetsMethod(invocation);
			if(obj!=null){
				return obj;
			}
		}
		return invocation.proceed();
	}
	
	/**
	 * 拦截器之拦截StatementHandler方法
	 * @param invocation
	 */
	public void prepareMethod(Invocation invocation){
		StatementHandler statementHandler = (StatementHandler) invocation.getTarget();  
        MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);  
        // 分离代理对象链(由于目标类可能被多个拦截器拦截，从而形成多次代理，通过下面的两次循环  
        // 可以分离出最原始的的目标类)  
        while (metaStatementHandler.hasGetter("h")) {  
            Object object = metaStatementHandler.getValue("h");  
            metaStatementHandler = SystemMetaObject.forObject(object);  
        }  
        // 分离最后一个代理对象的目标类  
        while (metaStatementHandler.hasGetter("target")) {  
            Object object = metaStatementHandler.getValue("target");  
           metaStatementHandler = SystemMetaObject.forObject(object);  
        }  
        MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");  
       
        BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");  
        // 获取要调用的mybatis SQL语句
        String sql = boundSql.getSql();  
        
        //要调用的mybatis映射文件中的id
        String mapperId = mappedStatement.getId();
        
        //匹配成功，拼分页SQL，并填充Pagination对象
        if(mapperId.matches(this.mapperName)){
        	
        	//获取Dao方法的参数
            Object parameterObject = boundSql.getParameterObject();
            if (parameterObject == null) {
                throw new NullPointerException("Pagination DAO's parameter can not be null!");  
            }
        	
            HashMap<String,Object> parameterMap = (HashMap<String,Object>)parameterObject;
            Object pagination = (Pagination) parameterMap.get("pagination");
            if(pagination == null){
            	throw new NullPointerException("Pagination DAO's parameter 'pagination' can not be null!");  
            }
        	if(pagination instanceof Pagination){
        		Pagination page = (Pagination)pagination;
        		
        		//重写sql  
        		String pageSQL = buildPageSQL(sql, page);
        		
        		//重写分页sql  
        		metaStatementHandler.setValue("delegate.boundSql.sql", pageSQL);
        		
        		Connection connection = (Connection) invocation.getArgs()[0];  
                // 重设分页参数里的总页数等  
                setPageParameter(sql, connection, mappedStatement, boundSql, page);
                
//                System.out.println(mapperId+"="+pageSQL);
        	}
//        	System.out.println("Maches is true,let us patination!");
        }
	}
	
	/**
	 * 拦截器之拦截ResultSetHandler方法
	 * @param invocation
	 */
	public Object handleResultSetsMethod(Invocation invocation){
		
		Object obj = null;
		//通过invocation获取代理的目标对象  
	    Object target  = invocation.getTarget(); 
	    Statement stmt = (Statement) invocation.getArgs()[0];

	    ResultSetHandler resultSetHandler = (ResultSetHandler) target; 
//		//利用反射获取到FastResultSetHandler的ParameterHandler属性，从而获取到ParameterObject；  
		ParameterHandler parameterHandler = (ParameterHandler) MyReflectUtil.getFieldValue(resultSetHandler, "parameterHandler");  
		Object parameterObj = parameterHandler.getParameterObject();  
		if (parameterObj == null) {
            throw new NullPointerException("Pagination DAO's parameter can not be null!");  
        }
		HashMap<String,Object> parameterMap = (HashMap<String,Object>)parameterObj;
        Object pagination = (Pagination) parameterMap.get("pagination");
        if(pagination == null){
        	throw new NullPointerException("Pagination DAO's parameter 'pagination' can not be null!");  
        }
	    
        if(pagination instanceof Pagination){
    		Pagination page = (Pagination)pagination;
    		System.out.println("！！！！"+page.getCurPageNo());
    	}
        
	    return obj;
	}
	
	/**
	 * 填充 Pagination
	 * @param sql
	 * @param connection
	 * @param mappedStatement
	 * @param boundSql
	 * @param page
	 */
	private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement,  
	        BoundSql boundSql, Pagination page) {  
	    // 记录总记录数  
	    String countSql = "select count(0) from (" + sql + ") total";  
	    PreparedStatement countStmt = null;  
	    ResultSet rs = null;  
	    try {  
	        countStmt = connection.prepareStatement(countSql);  
	        BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql,  
	                boundSql.getParameterMappings(), boundSql.getParameterObject());  
	        setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject());  
	        rs = countStmt.executeQuery();  
	        int totalCount = 0;  
	        if (rs.next()) {  
	            totalCount = rs.getInt(1);  
	        }  
	        page.setTotalCount(totalCount);  
	        int totalPage = totalCount / page.getPageSize() + ((totalCount % page.getPageSize() == 0) ? 0 : 1);  
	        page.setTotalPage(totalPage);  
	    } catch (SQLException e) {  
	    	System.out.println("=======>查询总数量报错！");
	    	e.printStackTrace();
	    } finally {  
	        try {  
	            rs.close();  
	        } catch (SQLException e) {  
	        }  
	        try {  
	            countStmt.close();  
	        } catch (SQLException e) {  
	        }  
	    }  
	}  

	@Override
	public Object plugin(Object target) {
		// TODO Auto-generated method stub  || target instanceof ResultSetHandler
		// 当目标类是StatementHandler类型时，才包装目标类，否者直接返回目标本身,减少目标被代理的  
		// 次数 
		if(target instanceof StatementHandler){
			return Plugin.wrap(target, this);  
		}
		return target;  
	}

	@Override
	public void setProperties(Properties arg0) {
		// TODO Auto-generated method stub
		String mapperName = arg0.getProperty("mapperName");
		String dbType = arg0.getProperty("dbType");
		this.mapperName = mapperName;
		this.dbType = dbType;
	}
	
	/**
	 * 形成分页的SQL语句
	 * @param sql
	 * @param pagination
	 * @return
	 */
	public String buildPageSQL(String sql,Pagination pagination){
		int curPageNo = pagination.getCurPageNo();
		int pageSize  = pagination.getPageSize();
		StringBuffer pSQL = new StringBuffer();
		pSQL.append("select page_tmp.* from (");
		pSQL.append(" select  rownum rowns,page_.* from( ");
		pSQL.append(sql);
		pSQL.append(" ) page_");
		pSQL.append(" ) page_tmp");
		pSQL.append(" where page_tmp.rowns>"+(curPageNo-1)*pageSize+" and page_tmp.rowns<="+(curPageNo*pageSize));
		return pSQL.toString();
	}
	
	/** 
	 * 对SQL参数(?)设值 
	 *  
	 * @param ps 
	 * @param mappedStatement 
	 * @param boundSql 
	 * @param parameterObject 
	 * @throws SQLException 
	 */  
	public void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,  
	        Object parameterObject) throws SQLException {  
	    ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);  
	    parameterHandler.setParameters(ps);  
	} 

}
